Skip to content

基于Puppeteer的前端主动监控系统

该项目是2020年5~9月份在网易传媒工作时,主导开发的内部系统。

项目背景

web前端页面所处的环境是很复杂的,不同的平台(桌面、移动)、不同的操作系统(Windows、macOS、Android、iOS、华为鸿蒙等)、不同的宿主环境(各种浏览器、各种app webview)。复杂的环境,导致前端页面极易出现异常问题。

00-background.png

前端团队经常收到来自运营人员收集的反馈:线上的某个页面出现了白屏、点击了页面的某个按钮没有反应、页面上某个区域没有正常展示出来、某个页面的排版错乱等问题。由于公司的业务产品往往面对着千万级别的用户,上面的问题对用户体验造成的伤害非常大,如果不及时处理好这些低级问题,很可能导致大批用户的流失。如何才能避免一些低级的缺陷不暴露给用户,让我们的开发团队、测试团队、产品团队以及运营团队的人员第一时间发现问题, 进而采取相应的技术补救措施,在用户无感知的情况下就把系统缺陷修复了呢?

当然为了保证系统的稳定性,前端监控是必不可少的环节。前端监控涉及的面比较广,主要包括:性能监控、异常监控和行为监控等,是一个老生常谈的话题了。

  • 性能监控:侧重于页面的性能指标,比如加载耗时、解析耗时、请求耗时,渲染耗时等
  • 异常监控:指页面的加载是否出现异常,交互行为是否符合预期
  • 行为监控:侧重用产品用户的操作轨迹,以及页面的访问量PV、UV等

监控的形式,主要分为两类:主动监控 和 被动监控。

  • 被动监控,基于真实用户场景,通常经过页面埋点、数据上报、统计分析和生成报警等环节。
  • 主动监控,基于仿真模拟环境,通过主动嗅探页面的运行状态达到实时监测的目的。

目前网易传媒的前端团队,采用sentry作为监控的技术方案,是一个典型的被动监控的技术方案。

02-sentry.png

基于 Sentry 的异常监控也存如下的缺陷:无法捕获到网络异常的错误,如页面中 依赖的静态资源加载错误或数据接口请求异常;无法感知页面的排版布局,如页面展示出现错位、遮挡、缺失等情况;依赖真实用户场景,在断网情况下监测立即失效。 而且存在报警延迟高的问题,需要用户端上报后才能接收到报警,无法提前处置。

业务需求

基于上面的缺陷,产生了业务需求。系统主要用于解决线上系统的突发异常无法及时追踪响应的问题。系统需要达成如下目标:

  • 搭建一套主动监控系统来解决当前团队所面临的问题,提升业务系统的稳定性,降低因异常造成的影响。
  • 增加一种前端异常的预警机制,当异常发生时,项目的相关人员能在第一时间内收到报警,避免异常传播到用户那里才收到反馈,提升异常响应的及时性。
  • 实现更加细粒度和可定制化的异常监测,在页面资源的加载、脚本的运行、依赖的数据接口、排版布局等方面实现有效的监测。

系统需要满足的业务流程如下图所示

03-business-flow.png

首先用户填写待检测页面的地址、选择检测的指标以及阈值范围,之后提交到系统后可直接预览单次的检测结果,接着用户设置定时监视的配置信息来提交定时任务, 然后定时任务进入到后台服务并由调度程序推送到仿真模拟环境运行,接下来判断本次的运行结果是否符合用户的预期设定,如果发生异常状况则进行通知,用户会收到报警,如果没有则结束本次流程。

04-user-case.png

系统划分出管理员和普通用户两类角色。普通用户,可以检测页面和接口,设置定时任务,查看报警等操作。管理员,具有更多的操作权限,可以设置普通用户的操作权限。

方案设计

系统整体的逻辑架构,如下图所示,分为主体功能和辅助功能。用户直观看到的视图功能主要是页面监测和接口监测,在系统的前端页面上主要是针对这两项功能的操作。页面监测和接口监测包含相似的功能点,都有检测参数的配置,根据具体需求填写配置;单次检测,能够快速看到配置对应的预览效果;如果想永久保留检测参数的配置,则可以保存成定时监视任务,由后台服务自动监测。页面监测包括静态资源、布局排版和脚本运行三个方面,接口监测是针对前端页面依赖的后端接口的细化检测,可以做针对性的校验,由用户提供校验规则。页面监测的筛查范围更广,接口监测更加有针对性。

06-architect.png

系统的核心是服务逻辑,主要包括任务管理、监测服务和报警服务三项,是视图逻辑功能的基础。主要运行在后端工程上,以 HTTP 数据接口的形式为前台提供服务。任务调度,对页面监测和接口监测所提交的定时任务进行处理,包括对任务数据的扫描,判断任务是否到达预定的执行时间,将任务解析成可以派发的格式,最后将任务推送到仿真环境。监测服务,用于承接任务调度流转过来的数据,将上一步转化后的任务数据进一步加工处理,并交由 Puppeteer 构建的仿真环境运行,记录运行过程中的各项指标, 校验是否在用户配置的阈值范围内,若结果异常则进入报警环节。报警服务,处理来自监测服务流转的数据,在监测服务中“生成报警”环节结束后,则进入到了报警服务的处理流程中。报警服务,包括实时报警、报警接入、报警下发和报警存档四个环节。

系统的功能模块划分如下图所示,分为页面监测、接口监测、任务管理、监测服务和报警查阅五个核心模块。

05-module.png

页面监测模块在视图上体现为链接检测、页面内容检测、单次整体检测、生成定时任务等子功能,是用户使用频率最高的功能,涉及到的检测维度多。接口监测,分为单次接口嗅探和生成监测任务两个子模块,单次接口的嗅探检测是一次性的检测数据接口并校验正确性,生成监测任务则是把单次的检测行为转化成定时任务。任务管理,包括任务列表、添加任务、编辑任务、查看详情、授权任务、搜索任务以及删除任务等子功能。监测服务模块,以后台服务的形式运行,能支持的报警类别有短信报警、IM报警、平台消息报警、邮件报警等。报警查阅模块,支持各种图表的分析,添加自定义指标统计、查看报警历史记录、搜索报警消息等子功能。

系统涉及到的数据模型,如下图所示。主要有:系统用户、监测任务、检测规则、报警记录、任务标签以及统计结果。

08-db-entity.png

监测任务是系统的核心实体,与其它实体产生的关联最多。监测任务实体和系统用户、报警记录、任务标签、检测规则这四个实体产生关联。一个系统用户可以创建多个监测任务,同时一个监测任务可以被授权给多个用户,所以两者是多对多的关系。一个监测任务可以拥有多个任务标签,同时一个任务标签也可以被多个监测任务所标记,所以两者同样是多对多的关系。一个监测任务会拥有多条用户设置的检测规则,所以监测任务和检测规则两个实体是一对多的关系。同时一个监测任务会产生多条报警记录,两者是一对多的映射关系。

一个系统用户可以创建多个任务标签,所以系统用户和任务标签两个实体是一对多的关系。一条报警记录可能触发了多条检测规则,两个实体是一对多的关系。多条报警记录生成一个统计结果,所以统计结果和报警记录两个实体是一对多的关系。

工程实现

系统采用前后端分离的架构实现,前后端划分成独立的项目工程。前端工程采用vue框架搭建,整个 项目模板由 Vue CLI 脚手架生成,上层视图部分以组件化的方式组织功能模块。使用 NPM 工具库来管理依赖包,整个工程由 webpack 构建和打包,使用Git做源代码的版本管理.

NPM 以及webpack共同构成了工程的基础支撑层,中间层是基于Vue 框架的 MVVM 架构设计,上层是业务组件。前端工程整体是一个单页面应用, 不同视图之间的跳转通过前端路由实现,这里主要依赖 Vue Router 库实现不同路径渲染不同的视图组件。使用了 Vuex 库统一管理前端工程的数据模型,实现组件之间的数据流转。

09-project-impl.png

基于 NodeJS 生态中的 Koa 框架,分为控制器层、服务层、持久层以及拦截切面 层。其中 Koa 框架是核心基础,围绕着该框架集成了一系列的中间件,用于请求流转的横向处理,各个业务层的职责明确清晰。控制器层,用于请求的转发和响应,请求参数的格式校验,以及响应结果的缓存处理等。服务层,是业务逻辑的主要实现层,包括各种接口服务、后台任务及实体关联等。持久层,负责和数据库的交互, 将系统产生的模型数据持久化到数据库,涉及到关系型数据到模型对象的映射, 为提高数据库的访问效率使用了数据库连接池。

10-backend-arch.png

Puppeteer 在后台工程中用于构建 Web 前端页面的仿真运行环境,配合实现监测服务,是整个服务逻辑的核心。PM2 是守护后台应用的进程管理器,在应用出现异常时,重启应用;内置的负载均衡器可同时管理多个进程,确保无需停止服务即可完成系统的重启和升级。

系统的核心数据字典如下图所示。

07-data-dict.png

本系统不涉及数据库的事物处理,实体之间的关系在程序中维护。任务表,存储用户设定的报警任务(检测的页面或接口地址、执行时机、触发条件等),报警任务包含的属性信息较多,上图中并未列全。任务表是本系统的核心表,它关联了规则表和标签表。规则表,用来存储报警任务的指标和规则区间,当读取一条报警任务时会将任务对应的多条限定规则一并读取了。标签表,比较简单,主要用来对任务做分类,此外还存储了一些辅助模块的标签数据。当系统产生了报警,除了向关联任务的报警接收人发送消息,还会将报警信息存储入库,以便查看报警历史,这时就用到上面所示的报警表了。报警表,会关联到用户表和任务表。最后一张用户表,关联了任务表和报警表,用来存储系统用户的信息以及相应的权限,用户登录系统后只会查看到自己拥有权限的任务和报警历史记录。

关键问题

在系统开发和落地的过程中遇到的关键问题及解决方案。

仿真运行环境

系统的仿真模拟运行环境,是基于 Puppeteer 工具库构建的部署在后台服务上的用于模拟运行 Web 前端系统的执行环境,底层是一个 Chromium 浏览器,通过 CDP (Chrome DevTools Protocol,Chrome 开发工具协议)来实现 Puppeteer 和浏览器之间 的通信。在浏览器中手动执行的大部分操作,都可以使用 Puppeteer 来模拟完成,比如生成网页的截图快照;点击页面上的某处按钮或链接;敲击键盘输入字符;提交某个表单;跳转到其它地址的页面等诸多行为。

11-mock-env.png

后台的监测服务通过调用 Puppeteer 提供的API,来操纵服务端的浏览器模拟运行待检测的页面,发送各种指令,完成页面各项指标的检测。仿真环境在获得定时调度模块传递的页面监测任务后,首先解析出里面包含的页面地址和各项指标规则,然后由 Puppeteer 调用 Chromium 启动一个标签页实例去运 行页面,最后收集运行的执行结果,和任务预设的各项参数值对比。在页面运行期间, Chromium 执行的各项操作都会完整的记录下来

页面排版布局的检测

Web 页面排版布局的检测,历来是相对困难的,因为存在很多不确定性因素。 Web 页面主要由 HTML、JavaScript、CSS 三部分组成,HTML 构建视图的骨骼框架, CSS 展示页面的样式外观,JavaScript 呈现页面的动态交互,三者相互配合共同决定了 页面的排版布局。任何一方出现问题,都会导致布局的错乱,此外不同浏览器的渲染规则也在一定程度上也会影响视觉效果。浏览器解析 HTML 生成的 DOM 树和 CSS 生成的 CSSOM 树结合,组成一颗渲染树,经过绘制的过程最终呈现在屏幕上。根据浏览器解析页面的原理及过程, 本系统提供了基于图片相似度对比和关键结点路径对比的两种方案,实现了对页面排版布局的有效检测。

12-layout-check.png

基于图片相似度对比的方案,其流程如图的上半部分所示,由用户上传一张待检测页面的样本图片(即 Web 页面正常展现情况下的截图),然后在系统上设定相似度的阈值参数,之后页面进入到仿真环境模拟运行,等到页面完全展示出来之后, 会调用 Puppeteer 提供的接口生成一张页面的截图,最后用在仿真环境下生成的这张截图和样本图片做相似度对比,如果对比结果低于设定的阈值,则认为页面的排版布局是异常的。这种方案适合动态交互少的情况,比如新闻资讯、图文博客等静态内容偏多的 Web 站点。

基于关键路径对比的方案,其流程如图的下半部分所示。首先,用户在提交 页面监测时填写页面渲染后的关键结点路径以及关键结点的属性信息,这些信息组织成JSON格式,传递到后台监测模块,和经过仿真环境运行生成的文档对象对比,通过判断两者是否匹配,来确定页面排版布局是否正常。这种方案,适合动态交互多的 Web 系统,比如互动评论页、多人协作的编辑页、轮播类的页面等。虽然里面的内容变了,呈现的视觉效果发生较大改变,但正常情况下生成的关键结点的路径不会变化,采用该方案依然奏效,而采用基于图片相似度对比的方案在这种情况下就会失效。

基于需求的实时监测

系统设计之初面临的一个重要问题。只有满足用户需求的实时监测,才能做到对线上系统的持续监视,及时了解到系统的运行状况,一旦有异常出现及时修复。至于如何定义实时监测中“实时”这一概念,不同的人有不同的理解,在我们的方案中把选择权交给了用户。由用户设定不同的时间策略,实时监测方案如下图所示。

13-real-time.png

用户创建定时任务后,数据会持久化到数据库中,监测服务的定时扫描模块会不停地扫描这些任务数据,发现某些定时任务到达预定的执行时间后,将其推送到待执行队列,然后依次进入到仿真环境模拟运行,并记录期间的执行结果,随后对结果进行检查,有不符合预期的情况则产生报警,最后将整个过程的关键环节记录到日志。 用户在创建监测任务时,根据业务项目的实际需求,选择不同的执行频率,系统提供了秒、小时、天、周等不同的选项,秒级的策略实时性最高,周级别的策略实时性最低。

资源使用的弹性伸缩

系统的后台服务要支撑很多的定时任务同时运行,而每一个页面监测任务在到达 既定的执行时间后都需要提供仿真环境,每个仿真环境背后都是一个 Chromium 进程实例,每个进程实例占用的系统资源(尤其是内存消耗)很大。一旦页面监测的定时任务过多,或者定时任务设置的执行频率过高,就会导致服务端系统资源的使用率飙升,监测系统的响应就会变慢。因此该问题,关系到系统的稳定性和可靠性。

14-running-pool.png

在服务后台设计了一个仿真环境的进程池管理模块,其设计思想借鉴了数据库连接池以及线程池的理念。在后台服务启动时,会预热一些 Chromium 进程实例,放入到进程池中。每个进程对象都进行了定制化的封装,即携带了存活时间、空闲状态、 激活次数等属性。当使用高峰到来时,急需大量的仿真运行环境,进程池中已经没有空闲的实例可用时,就会新增一些进程实例,满足调用方的需求。该过程不断重复, 达到可以容纳的进程数上限为止。当使用低谷时,进程池中有大量空闲的进程实例, 则会自动回收,以释放服务器端的系统资源。通过该方案,实现了对系统资源使用的弹性伸缩,保证了系统响应的及时性。

另外,针对用户创建的定时任务做了一些策略性的限制,避免无节制地创建高频率的定时任务,浪费系统资源。单个用户创建的任务数过多时,会有告警提醒,当严重超标时,则需要系统管理员审批。此外配合部署的 docker环境,可以在云环境上更进一步做到系统资源使用的弹性伸缩.

系统效果

15-effect-page.png

创建好的页面检测任务列表,会展示对应任务的报警次数/执行次数、更新时间,以及操作功能项。

16-alarm_history.png

登录系统,可查看到创建项目的报警情况,支持按时间维度的检索。

17-alarm-image.png

关联上了网易内部的即时通讯工具---泡泡,收到来自页面检测模块发出的报警信息。

18-alarm-api.png

监测到数据接口出现异常,通过“泡泡”及时通知到。

总结

前端页面在亿万终端设备上运行,而部署在单一环境下的主动监控系统是无法模拟出这种多样性和差异性的。比方说,用户终端设备的网络情况(2G~5G数据网络、WIFI、有线宽带)差异很大,必定造成网络请求的响应时间差异很大;用户终端设备的硬件配置不同,必定造成页面执行渲染的耗时不同;用户终端设备的系统环境比较复杂(可能同时开着多个App,跑着多个应用软件),必定造成页面的运行状态波动较大。

主动监控的价值体现在有针对性的检测,对严重问题的及时发现和报警,避免将严重的缺陷传播到用户那里。因此,它是对常规的被动监控模式的有效补充,有了它整个前端监控体系变得更加完善。